/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2022. All rights reserved.
 * Description: 
 * Author: g00421808
 * Create: 12/5/2022
 * Notes: 
 */

#ifndef JSBIND_NAPI_PFUNCTION_BINDER_H
#define JSBIND_NAPI_PFUNCTION_BINDER_H
#include "jsbind/binder/function_binder.h"
#include "jsbind/config.h"

namespace Jsb {

template <typename F, typename R, typename... P>
class NapiPFunctionBinder : public FunctionBinder<NapiPFunctionBinder<F, R, P...>, F, R, P...> {
public:
    typedef FunctionBinder<NapiPFunctionBinder<F, R, P...>, F, R, P...> ParentType;

    static napi_value Wrapper (napi_env env, napi_callback_info info) {
        JSB_DLOG(DEBUG) << "NapiPFunctionBinder::Wrapper";
        if (!Checker<P...>::IsArityValid(env, info)) {
            std::string msg = std::string("JSBind: Wrong number of arguments, expected: ").append(std::to_string(sizeof...(P)));
            napi_throw_error(env, nullptr, msg.c_str());
            return nullptr;
        }
        if (!Checker<typename ValueDefiner<P>::RawType...>::TypesAreValid(env, info)) {
            std::string msg = Checker<typename ValueDefiner<P>::RawType...>::GetTypeError(env, info);
            napi_throw_type_error(env, "JSBind: Wrong type of arguments", msg.c_str());
            return nullptr;
        }
        return SafetyWrapper(env, info, std::make_index_sequence<sizeof...(P)>());
    }

private:
    struct InvokeWorkInfo {
        // napi work
        napi_async_work worker;
        // napi deferred
        napi_deferred deferred;

        Jsb::BindInfo* bindInfo;

        std::tuple<typename ValueDefiner<P>::RawType...> params;

        R result;
    };

    template<size_t... Index>
    static napi_value SafetyWrapper(napi_env env, napi_callback_info info, std::index_sequence<Index...>) {
        napi_status status;
        Jsb::BindInfo* bindInfo = nullptr;
        size_t argc = sizeof...(Index);

        std::array<napi_value, sizeof...(Index)> args;
        status = napi_get_cb_info(env, info, &argc, args.data(), nullptr, ((void**)&bindInfo));
        JSB_DCHECK(status == napi_ok);
        JSB_DCHECK(bindInfo != nullptr);
        auto tuple = std::make_tuple(NapiValueMaker<typename ValueDefiner<P>::RawType>(env, args[Index])...);
        auto valueTuple = std::make_tuple(NapiValueTrait<typename ValueDefiner<P>::RawType>::Cast(std::get<Index>(tuple))...);

        // 异步执行
        struct InvokeWorkInfo* workData = new(std::nothrow) InvokeWorkInfo;
        workData->bindInfo = bindInfo;
        workData->params = valueTuple;

        return AsyncForward(env, workData);

    }

    static napi_value AsyncForward(napi_env env, InvokeWorkInfo* workData) {
        napi_status status;
        napi_value promise;
        napi_value workName;
        status = napi_create_promise(env, &workData->deferred, &promise);
        JSB_DCHECK(status == napi_ok);
        status = napi_create_string_utf8(env, "JSBind Deferred Promise from Async Work Item", NAPI_AUTO_LENGTH, &workName);
        JSB_DCHECK(status == napi_ok);
        status = napi_create_async_work(env,
                                        nullptr,
                                        workName,
                                        AsyncInvokeWork,
                                        AsyncInvokeDone,
                                        (void*) workData,
                                        &workData->worker);
        JSB_DCHECK(status == napi_ok);
        status = napi_queue_async_work(env, workData->worker);
        JSB_DCHECK(status == napi_ok);

        return promise;
    }

    // native方法调用 子线程任务
    static void AsyncInvokeWork(napi_env env, void *data)
    {
        JSB_DCHECK(data != nullptr);
        struct InvokeWorkInfo *arg = (struct InvokeWorkInfo *)data;

        arg->result = WrapperForward(arg, std::make_index_sequence<sizeof...(P)>());
    }

    // native方法调用 结果回调 回到主线程
    static void AsyncInvokeDone(napi_env env, napi_status status, void *data)
    {
        JSB_DCHECK(status == napi_ok);
        JSB_DCHECK(data != nullptr);
        struct InvokeWorkInfo *arg = (struct InvokeWorkInfo *)data;

        napi_value result = nullptr;
        // 失败调用返回错误码
        if constexpr (!std::is_void<R>::value) {
            result = NapiValue<R>::ToNapiValue(env, arg->result);
        }

        if (!result) {
            napi_get_undefined(env, &result);
        }

        status = napi_resolve_deferred(env, arg->deferred, result);
        JSB_DCHECK(status == napi_ok);
        status = napi_delete_async_work(env, arg->worker);
        JSB_DCHECK(status == napi_ok);
        arg->deferred = nullptr;
        delete arg;
    }

    template<size_t... Index>
    static R WrapperForward(struct InvokeWorkInfo *arg, std::index_sequence<Index...>) {

        Jsb::BindInfo* bindInfo = arg->bindInfo;
        std::tuple<typename ValueDefiner<P>::RawType...>& params = arg->params;
        return ParentType::InnerWrapper(bindInfo, std::get<Index>(params)...);
    }
};

} // namespace Jsb
#endif //JSBIND_NAPI_PFUNCTION_BINDER_H
